// Copyright (c) 2021 Presto Labs Pte. Ltd.
// Author: leon

#pragma once

#include <string>

#include <rapidjson/document.h>
#include <rapidjson/rapidjson.h>

#include "coin/proto/coin_market_enums.pb.h"
#include "coin2/base/conversion.h"
#include "coin2/exchange/base/feed/update.h"
#include "coin2/exchange/base/feed_util/book_builder.h"
#include "coin2/exchange/base/feed_util/trade.h"

namespace impl {
using coin2::exchange::base::market::ExchangeType;
using coin2::exchange::base::market::MarketType;

class BtcturkFeedUpdateParser {
 public:
  static void ParseSnapshot(
      BookBuilder* book_builder,
      const rapidjson::Document& book_json,
      int64_t timestamp) {
    auto tx = book_builder->NewTransaction(timestamp);
    tx.Clear();

    for (const auto& ask_obj : book_json["AO"].GetArray()) {
      double price = stod(ask_obj["P"].GetString());
      double size = stod(ask_obj["A"].GetString());
      tx.UpdateAsk(price, size);
    }

    for (const auto& bid_obj : book_json["BO"].GetArray()) {
      double price = stod(bid_obj["P"].GetString());
      double size = stod(bid_obj["A"].GetString());
      tx.UpdateBid(price, size);
    }
  }

  static void ParseUpdate(
      BookBuilder* book_builder,
      const rapidjson::Document& book_json,
      int64_t timestamp) {
    auto tx = book_builder->NewTransaction(timestamp);

    for (const auto& ask_obj : book_json["AO"].GetArray()) {
      double price = stod(ask_obj["P"].GetString());
      double size = stod(ask_obj["A"].GetString());
      if (ask_obj["CP"].GetInt() == 3) {
        // CP = 3 is delete case.
        size = 0;
      }
      tx.UpdateAsk(price, size);
    }

    for (const auto& bid_obj : book_json["BO"].GetArray()) {
      double price = stod(bid_obj["P"].GetString());
      double size = stod(bid_obj["A"].GetString());
      if (bid_obj["CP"].GetInt() == 3) {
        // CP = 3 is delete case.
        size = 0;
      }
      tx.UpdateBid(price, size);
    }
  }

  static void ParseTrade(
      const rapidjson::Document& trade_json,
      Trade* trade) {
    trade->timestamp = stoull(trade_json["D"].GetString()) * 1'000'000LL;
    trade->price = stod(trade_json["P"].GetString());
    trade->fill_qty = stod(trade_json["A"].GetString());
    trade->side = ParseTradeSide(trade_json["S"].GetInt());
  }

  static coin2::exchange::feed::TradeSide ParseTradeSide(int side) {
    if (side == 0) {
      return coin2::exchange::feed::TRADE_BUY_SIDE;
    } else if (side == 1) {
      return coin2::exchange::feed::TRADE_SELL_SIDE;
    } else {
      throw std::runtime_error("[Btcturk] unknown side: " + side);
    }
  }

  static fastfeed::proto::KlineInterval ParseKlineInterval(const std::string& interval) {
    static std::map<std::string, fastfeed::proto::KlineInterval> interval_map = {
      {"1", fastfeed::proto::KLINE_INTERVAL_1HOUR},
      {"15", fastfeed::proto::KLINE_INTERVAL_1HOUR},
      {"60", fastfeed::proto::KLINE_INTERVAL_1HOUR},
      {"1D", fastfeed::proto::KLINE_INTERVAL_1DAY},
    };
    auto iter = interval_map.find(interval);
    if (iter == interval_map.end()) {
      
      return fastfeed::proto::KLINE_INTERVAL_INVALID;
    }

    return iter->second;
  }

    static bool Parse1Kline(
      const rapidjson::Value& open_timestamp,
      const rapidjson::Value& open,
      const rapidjson::Value& high,
      const rapidjson::Value& low,
      const rapidjson::Value& close,
      const rapidjson::Value& volume,
      Klines* klines,
      int64_t timestamp,
      fastfeed::proto::KlineInterval interval) {
    int64_t open_timestamp_value = open_timestamp.GetInt64() * 1'000'000'000LL;
    auto result = klines->AddKline(open_timestamp_value);
    if (!result) return false;

    Kline* kline;
    bool freezed;
    std::tie(kline, freezed) = result.value();

    kline->open = open.GetDouble();
    kline->high = high.GetDouble();
    kline->low = low.GetDouble();
    kline->close = close.GetDouble();
    kline->volume = volume.GetDouble();
    kline->timestamp = timestamp;
    kline->interval = interval;
    return freezed;
  }
};
}  // namespace impl

using impl::BtcturkFeedUpdateParser;
